package AFX::ACF;

=head1 NAME

AFX::ACF - ACF Processing

  add_passwd()               - Add password
  check_site_passwd()        - Check all site's passwords
  DESTROY()                  - ACF destructor
  encrypt_passwd()           - Encrypt all non-encrypted passwords
  find_asf()                 - Find ASF
  find_atf()                 - Find ATF/STF
  get_directory()            - Return directorie's path for id
  get_file()                 - Return filename for id
  get_list_directory()       - Return list of directories
  get_list_file()            - Return list of files
  get_list_machine()         - Return list of machines
  get_list_passwd()          - Return list of passwd
  get_list_process()         - Return list of processes
  get_list_site_passwd()     - Return list of site's passwd
  get_machine()              - Return ACF machine for specified unit
  get_process()              - Return process for Process Identifier
  get_prompt()               - Return prompt for login
  get_site_passwd()          - Return site's passwd for login
  get_terminal()             - Return terminal for id
  get_title()                - Return ACF Title
  get_version()              - Return ACF Version
  logging()                  - Logging and screen printing
  message_empty_passwd()     - Message: Empty password in Site's config file found
  message_failed_keyfile()   - Message: Keyfile not found.
  message_failed_library()   - Message: Failed loading library.
  message_failed_logfile()   - Message: Logfile failed opening
  message_failed_open()      - Message: ACF failed opening
  message_failed_passwd()    - Message: Keyfile not found or encryption broken
  message_failed_process()   - Message: Process failed executing
  message_failed_sitefile()  - Message: Site's Id file not found.
  message_failed_unit()      - Message: Unit is not supported
  message_failed_valid()     - Message: Failed validation
  message_passwd_encrypted() - Message: Password for unit as login encrypted
  message_title()            - Message: Title
  message_updated()          - Message: ACF updated successfully
  message_uptodate()         - Message: ACF up-to-date
  new()                      - ACF contructor
  open_keyfile()             - Open Key File for decrypt/encrypt passwords
  open_sitefile()            - Open Default Site's Identifier File
  purge()                    - Purge old ARFs and logfiles
  set_sitefile()             - Set Default Site's Identifier File
  update()                   - Update ACF
  validate_file()            - Validate File using XSD (XML Schema)
  validate_string()          - Validate String using XSD (XML Schema)

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2010 Mikhail Volkov

This file is part of AuditFX.

    AuditFX is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    AuditFX is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with AuditFX.  If not, see <http://www.gnu.org/licenses/>.
=cut

use strict;
use Carp;
use Exporter ();

use vars qw($VERSION);
my @ISA = qw(Exporter);
$VERSION = '1.0';

##############################################################################
# Globals
#
use POSIX qw(strftime);
use Crypt::Blowfish_PP;
use Crypt::CBC;
use XML::Simple;
use XML::SAX::ParserFactory;
use XML::Validator::Schema;

##############################################################################
#
# add_passwd()
#
# Add password
#
# Return:
# - 1          - if password added
# - 0          - otherwise
#
# Arguments expected are:
# - self       - Object reference
# - passwd     - Password Hash

sub add_passwd {
    my ( $self, $passwd ) = @_;

## Return error, if session invalid
    return 0 if !$passwd or !$passwd->{login} or !$passwd->{content};

## Add session
    push( @{ $self->{_xml}->{passwd} }, $passwd );

## Set updated flag
    $self->{updated}++;

## Return Ok
    return 1;

} ## end sub add_passwd

##############################################################################
#
# check_site_passwd()
#
# Check all site's passwords
#
# Return:
# - 0             - if invalid passwords exist
# - 1             - if no passwords or all of them correct
#
# Arguments expected are:
# - self          - Object reference

sub check_site_passwd {
    my ($self) = @_;

## Return Error, if no passwd found
    return 1 if !$self->get_list_site_passwd();

## Check passwd in ACF
    foreach my $passwd ( @{ $self->get_list_site_passwd() } ) {

## If invalid password found, return Error
        return 0
          if !$passwd->{login}
              or !$passwd->{unit}
              or !$passwd->{content}
              or $passwd->{content} eq "";
    }

## If passwords correct, return Ok
    return 1;

} ## end sub check_site_passwd

##############################################################################
#
# DESTROY()
#
# ACF destructor
#
# Arguments expected are:
# - self         - Object reference

sub DESTROY {
    my ($self) = @_;

##    print "ACF destroyed\n";

    1;

} ## end sub DESTROY

##############################################################################
#
# encrypt_passwd()
#
# Encrypt all non-encrypted passwords
#
# Return:
# - 1          - if passwords encrypted
# - 0          - otherwise
#
# Arguments expected are:
# - self       - Object reference

sub encrypt_passwd {
    my ($self) = @_;

## Open Keyfile
    my $cbc = $self->open_keyfile();

## If Keyfile not opened
    if ( !$cbc ) {

## Print Keyfile not found or encription broken message
        $self->message_failed_passwd();

## Return Error
        return 0;
    }

## Return Error, if no passwords found
    return 0 if !$self->get_list_passwd();

## Check passwd in ACF
    foreach my $passwd ( @{ $self->get_list_passwd() } ) {

## Skip if password not encrypted or empty
        next
          if !$passwd->{encrypted}
              or !$passwd->{content}
              or $passwd->{encrypted} eq "true";

## Encrypt password
        $passwd->{content} = $cbc->encrypt_hex( $passwd->{content} );

## Set encrypted flag to true
        $passwd->{encrypted} = 'true';

## Print Passwd encrypted message
        $self->message_passwd_encrypted( $passwd->{unit}, $passwd->{login} );

## Set updated flag
        $self->{updated}++;
    }

## Return Ok, if passwords encrypted
    return 1;

} ## end sub encrypt_passwd

##############################################################################
#
# find_asf()
#
# Find ASF
#
# Return:
# - asfnames    - list of ASFs
# - 0           - ASFs not found
#
# Arguments expected are:
# - self        - Object reference

sub find_asf {
    my ($self) = @_;

## Get ASF directory
    my $asfdir = $self->get_directory('site');

## Open directory; return if opening failed
    return 0 if !opendir( DIR, $asfdir );

## Find ASF
    my @files = grep( /\.xml$/, readdir(DIR) );

## Close directory
    closedir(DIR);

## If files not found return 0
    return 0 if !@files;

## Return ASF filenames
    return \@files;

} ## end sub find_asf

##############################################################################
#
# find_atf()
#
# Find ATF/SFT
#
# Return:
# - atfnames    - list of ATFs/STFs
# - 0           - otherwise
#
# Arguments expected are:
# - self        - Object reference
# - directory   - 'template' or 'scenario'
# - mask        - Regex mask for templates

sub find_atf {
    my ( $self, $directory, $mask ) = @_;

## Get ATF/STF directory
    my $atfdir = $self->get_directory($directory);

## Return error if opening directory failed
    return 0 if !opendir( DIR, $atfdir );

## Find ATF/STF
    my @files = grep( /$mask\.xml$/, readdir(DIR) );

## Close directory
    closedir(DIR);

## If files not found return Error
    return 0 if !@files;

## Return filenames
    return \@files;

} ## end sub find_atf

##############################################################################
#
# get_directory()
#
# Return directories path for id
#
# Return:
# - Directory's fullname  - it exists in ACF
# - 0                     - otherwise
#
# Arguments expected are:
# - self                  - Object reference
# - directory_id          - Directory id

sub get_directory {
    my ( $self, $directory_id ) = @_;

## Return Error, if no directories found
    return 0 if !get_list_directory($self);

## Check directories in ACF
    foreach my $directory ( @{ get_list_directory($self) } ) {

## Skip wrong directory
        next if $directory->{id} ne $directory_id;

## Make directory unless it already exists
        mkdir( $directory->{id} ) unless -d $directory->{id};

## Return directory name
        return $directory->{content};
    }

## If directory not specified in ACF, return 0
    return 0;

} ## end sub get_directory

##############################################################################
#
# get_file()
#
# Return filename for id
#
# Return:
# - Filename     - if exists in ACF
# - 0            - otherwise
#
# Arguments expected are:
# - self         - Object reference
# - file_id      - File's identifier

sub get_file {
    my ( $self, $file_id ) = @_;

## Return Error, if no files found
    return 0 if !get_list_file($self);

## Check files in ACF
    foreach my $file ( @{ get_list_file($self) } ) {

## Skip wrong file
        next if $file->{id} ne $file_id;

## Return file name
        return $file->{content};
    }

## If file not specified in ACF, return Error
    return 0;

} ## end sub get_file

##############################################################################
#
# get_list_directory()
#
# Return list of directories
# - 0             - if list empty
#
# Arguments expected are:
# - self          - Object reference

sub get_list_directory {
    my ($self) = @_;

## Return Directories list
    return $self->{_xml}->{directory} if $self->{_xml}->{directory};

## Return Error
    return 0;

} ## end sub get_list_directory

##############################################################################
#
# get_list_file()
#
# Return list of files
# - 0             - if list empty
#
# Arguments expected are:
# - self          - Object reference

sub get_list_file {
    my ($self) = @_;

## Return File list
    return $self->{_xml}->{file} if $self->{_xml}->{file};

## Return Error
    return 0;

} ## end sub get_list_file

##############################################################################
#
# get_list_machine()
#
# Return list of machines
# - 0             - if list empty
#
# Arguments expected are:
# - self          - Object reference

sub get_list_machine {
    my ($self) = @_;

## Return Machine list
    return $self->{_xml}->{machine} if $self->{_xml}->{machine};

## Return Error
    return 0;

} ## end sub get_list_machine

##############################################################################
#
# get_list_passwd()
#
# Return list of passwd
# - 0             - if list empty
#
# Arguments expected are:
# - self          - Object reference

sub get_list_passwd {
    my ($self) = @_;

## Return Passwd list
    return $self->{_xml}->{passwd} if $self->{_xml}->{passwd};

## Return Error
    return 0;

} ## end sub get_list_passwd

##############################################################################
#
# get_list_process()
#
# Return list of processes
# - 0             - if list empty
#
# Arguments expected are:
# - self          - Object reference

sub get_list_process {
    my ($self) = @_;

## Return Process list
    return $self->{_xml}->{process} if $self->{_xml}->{process};

## Return Error
    return 0;

} ## end sub get_list_process

##############################################################################
#
# get_list_site_passwd()
#
# Return list of site's passwd
# - 0             - if list empty
#
# Arguments expected are:
# - self          - Object reference

sub get_list_site_passwd {
    my ($self) = @_;

## Return Passwd list
    return $self->{_site_xml}->{passwd}
      if $self->{_site_xml} and $self->{_site_xml}->{passwd};

## Return Error
    return 0;

} ## end sub get_list_site_passwd

##############################################################################
#
# get_machine()
#
# Return ACF machine for specified unit
#
# Return:
# - Machine hash    - it exists in ACF
# - 0               - otherwise
#
# Arguments expected are:
# - self            - Object reference
# - machine_unit    - Machine's unit

sub get_machine {
    my ( $self, $machine_unit ) = @_;

## Return Error, if no machines found
    return 0 if !$self->get_list_machine();

## Check machines in ACF
    foreach my $machine ( @{ $self->get_list_machine() } ) {

## Skip wrong machine
        next if $machine->{unit} ne $machine_unit;

## Return Machine hash
        return $machine;
    }

## If machine not specified in ACF, return Error
    return 0;

} ## end sub get_machine

##############################################################################
#
# get_process()
#
# Return process for Process Identifier
#
# Return:
# - Process hash  - if exists in ACF
# - 0             - otherwise
#
# Arguments expected are:
# - self          - Object Reference
# - process_id    - Process's Id

sub get_process {
    my ( $self, $process_id ) = @_;

## Return Error, if no processes found
    return 0 if !$self->get_list_process();

## Check processes in ACF
    foreach my $process ( @{ $self->get_list_process() } ) {

## Skip wrong process
        next if $process->{id} ne $process_id;

## Return Process hash
        return $process;
    }

## If process not specified in ACF, return Error
    return 0;

} ## end sub get_process

##############################################################################
#
# get_prompt()
#
# Return prompt for login
#
# Return:
# - Prompt hash   - if exists in ACF
# - 0             - otherwise
#
# Arguments expected are:
# - self          - Object reference
# - login         - Login

sub get_prompt {
    my ( $self, $login ) = @_;

## Check prompt in ACF
    foreach my $prompt ( @{ $self->{_xml}->{prompt} } ) {

## Skip wrong prompt
        next if $prompt->{login} ne $login;

## Return Prompt hash
        return $prompt;
    }

## If prompt not specified in ACF, return Error
    return 0;

} ## end sub get_prompt

##############################################################################
#
# get_site_passwd()
#
# Return site's passwd for login
#
# Return:
# - Passwd hash   - if exists in site's ACF
# - 0             - otherwise
#
# Arguments expected are:
# - self          - Object reference
# - login         - Login
# - unit          - Unit's Identifier

sub get_site_passwd {
    my ( $self, $login, $unit ) = @_;

## Return Error, if no passwd found
    return 0 if !$self->get_list_site_passwd();

## Check passwd in ACF
    foreach my $passwd ( @{ $self->get_list_site_passwd() } ) {

## Skip wrong passwd
        next if $passwd->{login} ne $login or $passwd->{unit} ne $unit;

## Return Passwd hash
        return $passwd;
    }

## If passwd not specified in site's ACF, return Error
    return 0;

} ## end sub get_site_passwd

##############################################################################
#
# get_terminal()
#
# Return terminal for id
#
# Return:
# - Terminal hash    - if exists in ACF
# - 0                - otherwise
#
# Arguments expected are:
# - self             - Object reference
# - terminal_id      - Terminal id

sub get_terminal {
    my ( $self, $terminal_id ) = @_;

## Check terminal in ACF
    foreach my $terminal ( @{ $self->{_xml}->{terminal} } ) {

## Skip wrong terminal
        next if $terminal->{id} ne $terminal_id;

## Return Terminal hash
        return $terminal;
    }

## If terminal not specified in ACF, return Error
    return 0;

} ## end sub get_terminal

##############################################################################
#
# get_title()
#
# Return ACF Title
#
# Return:
# - Title hash   - if exists in ACF
# - 0            - otherwise
#
# Arguments expected are:
# - self         - Object reference
# - title_id     - Title's Identifier

sub get_title {
    my ( $self, $title_id ) = @_;

## Check title in ACF
    foreach my $title ( @{ $self->{_xml}->{title} } ) {

## Skip wrong template
        next if $title->{id} ne $title_id;

## Return Title
        return $title;
    }

## If title not specified in ACF, return Error
    return 0;

} ## end sub get_title

##############################################################################
#
# get_version()
#
# Return ACF Version
#
# Return:
# - version      - Version
#
# Arguments expected are:
# - self         - Object reference

sub get_version {
    my ($self) = @_;

## Return Version
    return $self->{_xml}->{version};

} ## end sub get_version

##############################################################################
#
# logging()
#
# Logging and screen printing
#
# Return:
# - logged message
#
# Arguments expected are:
# - self     - Object reference
# - message  - Message text
# - screen   - will be printed and logged
# - log_id   - Log name, default 'unknown'

sub logging {
    my ( $self, $message, $screen, $log_id ) = @_;

## If log_id not specified, used default 'unknown'
    $log_id = "unknown" if !$log_id;

## Set logname and logdir
    my $logdir = get_directory( $self, 'log' );

## If logdir not specified, use log
    $logdir = 'log' if !$logdir;

## Generate logfile name
    my $today = strftime "%Y-%m-%d", localtime;

## Set logfile
    my $logfile = "$logdir/" . $today . "_$log_id.log";

## Open logfile and put message at the end
    if ( open( LOGFILE, ">> $logfile" ) ) {
        print LOGFILE $message . "\n";
        close(LOGFILE);
    }
    else {
        print message_failed_logfile( $self, $logfile ) . "\n";
    }

## Print in Wx
    eval { Wx::LogMessage($message); };

## Print message on screen, if specified
    print $message. "\n" if $screen;

## Return message
    return $message;

} ## end sub logging

##############################################################################
#
# message_empty_passwd()
#
# Message: Empty password in Site's config file found
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference

sub message_empty_passwd {
    my ($self) = @_;

## Set message
    my $message = "Site.config processing: ";
    $message .= "Empty password found. Please specify or delete it.";

## Return message
    return $self->logging( $message, 1, 'audit' );

} ## end sub message_empty_passwd

##############################################################################
#
# message_failed_keyfile()
#
# Message: Keyfile not found.
#
# Return:
# - logged message
#
# Arguments expected are:
# - self     - Object reference
# - keyfile  - Key filename

sub message_failed_keyfile {
    my ( $self, $keyfile ) = @_;

## Set message
    my $message = "Keyfile '$keyfile': not found";

## Return message
    return $self->logging( $message, 1, 'audit' );

} ## end sub message_failed_keyfile

##############################################################################
#
# message_failed_library()
#
# Message: Failed loading library.
#
# Return:
# - logged message
#
# Arguments expected are:
# - self      - Object reference
# - libname   - Library's name

sub message_failed_library {
    my ( $self, $libname ) = @_;

## Set message
    my $message = "Failed loading $libname library.";

## Return message
    return $self->logging( $message, 1, 'audit' );

} ## end sub message_failed_library

##############################################################################
#
# message_failed_logfile()
#
# Message: Logfile failed opening
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference
# - logfile      - Log's filename

sub message_failed_logfile {
    my ( $self, $logfile ) = @_;

## Set message
    my $message = "Log '$logfile': failed opening";

## Return message
    return $message;

} ## end sub message_failed_logfile

##############################################################################
#
# message_failed_open()
#
# Message: ACF failed opening
#
# Return:
# - logged message
#
# Arguments expected are:
# - self     - Object reference
# - error    - Error
# - file     - Config file

sub message_failed_open {
    my ( $self, $error, $file ) = @_;

## Set config file as default
    $file = $self->{file} if !$file;

## Set message
    my $message = "ACF $file: failed opening.\n";
    $message .= "Error: $error" if $error;

## Return message
    return logging( $self, $message, 0, 'audit' );

} ## end sub message_failed_open

##############################################################################
#
# message_failed_passwd()
#
# Message: Keyfile not found or encryption broken
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference

sub message_failed_passwd {
    my ($self) = @_;

## Set message
    my $message = "ACF processing: ";
    $message .= "Keyfile not found or encription broken";

## Return message
    return $self->logging( $message, 1, 'audit' );

} ## end sub message_failed_passwd

##############################################################################
#
# message_failed_process()
#
# Message: Process failed executing
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference

sub message_failed_process {
    my ($self) = @_;

## Set message
    my $message = "Process failed executing";

## Return message
    return $self->logging( $message, 1, 'audit' );

} ## end sub message_failed_process

##############################################################################
#
# message_failed_sitefile()
#
# Message: Site's Id file not found.
#
# Return:
# - logged message
#
# Arguments expected are:
# - self      - Object reference
# - sitefile  - Site's Id filename

sub message_failed_sitefile {
    my ( $self, $sitefile ) = @_;

## Set message
    my $message = "Sitefile '$sitefile': not found";

## Return message
    return $self->logging( $message, 1, 'audit' );

} ## end sub message_failed_sitefile

##############################################################################
#
# message_failed_unit()
#
# Message: Unit is not supported
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference
# - unit         - Unit's Identifier

sub message_failed_unit {
    my ( $self, $unit ) = @_;

## Set message
    my $message = "Unit $unit is not supported";

## Return message
    return $self->logging( $message, 1, 'audit' );

} ## end sub message_failed_unit

##############################################################################
#
# message_failed_valid()
#
# Message: Failed validation
#
# Return:
# - logged message
#
# Arguments expected are:
# - self     - Object reference
# - error    - Error

sub message_failed_valid {
    my ( $self, $error ) = @_;

## Remove newlines in error
    $error =~ s/\n//g;

## Set message
    my $message = "ACF '$self->{file}': failed validation.\n";
    $message .= "Error: $error" if $error;

## Return message
    return logging( $self, $message, 0, 'audit' );

} ## end sub message_failed_valid

##############################################################################
#
# message_passwd_encrypted()
#
# Message: Password for unit as login encrypted
#
# Return:
# - logged message
#
# Arguments expected are:
# - self   - Object reference
# - unit   - Unit's Id
# - login  - Login

sub message_passwd_encrypted {
    my ( $self, $unit, $login ) = @_;

## Set message
    my $message = "ACF processing: ";
    $message .= "password for $unit as $login encrypted";

## Return message
    return $self->logging( $message, 0, 'audit' );

} ## end sub message_passwd_encrypted

##############################################################################
#
# message_title()
#
# Message: Title
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference

sub message_title {
    my ($self) = @_;

## Get title
    my $name = $self->get_title('name');
    my $link = $self->get_title('link');

## Set message
    my $message = $name->{content} . ": " if $name;
    $message .= "Version " . $self->get_version() . ". ";
    $message .= "\n" . $link->{content} if $link;

## Return message
    return $message;

} ## end sub message_title

##############################################################################
#
# message_updated()
#
# Message: ACF updated successfully
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference

sub message_updated {
    my ($self) = @_;

## Set message
    my $message = "ACF processing: updated successfully";

## Return message
    return $self->logging( $message, 1, 'audit' );

} ## end sub message_updated

##############################################################################
#
# message_uptodate()
#
# Message: ACF up-to-date
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference

sub message_uptodate {
    my ($self) = @_;

## Set message
    my $message = "ACF processing: up-to-date";

## Return message
    return $self->logging( $message, 1, 'audit' );

} ## end sub message_uptodate

##############################################################################
#
# new()
#
# ACF contructor

sub new {

## Get package name
    my $type = shift;

## Define object's hash
    my $self = { file => 'AFX.config', directory => 'lib', updated => 0 };

## Set full pathname
    $self->{path} = $self->{directory} . "/" . $self->{file};

## If failed opening
    if ( !-f $self->{path} ) {

## Die with error
        die message_failed_open($self);
    }

## Validate ACF
    my $error = validate_file( $self, $self->{path} );

## Exit if ACF failed validation
    die message_failed_valid( $self, $error ) if $error;

## Create XML object
    $self->{xs} = XML::Simple->new(
        ForceArray   => 1,
        ForceContent => 1,
        KeyAttr      => []
    );

## Open ACF
    $self->{_xml} = eval { $self->{xs}->XMLin( $self->{path} ); };

## Exit if ACF failed opening
    if ( $@ or !$self->{_xml} or !$self->{_xml}->{version} ) {
        die message_failed_open( $self, "$@" );
    }

## Get ASF directory
    my $asfdir = get_directory( $self, 'site' );

## Get site config file name
    my $asffile = get_file( $self, 'config' );

## Set full pathname
    my $asfconfig = $asfdir . "/" . $asffile;

## Set empty site's xml
    $self->{_site_xml} = 0;

## If site's config config exists, open it
    if ( -f $asfconfig ) {

## Open site ACF
        $self->{_site_xml} = eval { $self->{xs}->XMLin($asfconfig); };

## Exit if Site's ACF failed opening
        if ( $@ or !$self->{_site_xml} ) {
            die message_failed_open( $self, "$@", $asfconfig );
        }
    }

## Return object
    return bless $self, $type;

} ## end sub new

##############################################################################
#
# open_keyfile()
#
# Open Key File for decrypt/encrypt passwords
#
# Return Crypt::CBC Instance
# - 0         - if keyfile not found or openning failed
#
# Arguments expected are:
# - self      - Object reference

sub open_keyfile {
    my ($self) = @_;

## Get keyfile name
    my $keyfile = $self->get_file('key');

## If keyfile not specified, return 0
    return 0 if !$keyfile;

## Open keyfile
    if ( !open( KEY, "< $keyfile" ) ) {

## Keyfile not found, print message
        $self->message_failed_keyfile($keyfile);

## Return Error
        return 0;
    }

## Get keyfile content
    my $key = join( '', <KEY> );
    close(KEY);
    chomp($key);

## If key is empty, return Error
    if ( !$key ) {

## Keyfile not found, print message
        $self->message_failed_keyfile($keyfile);

## Return Error
        return 0;
    }

## Get CBC instance
    my $cbc = Crypt::CBC->new( $key, 'Blowfish_PP' );

## If encryption failed, return Error
    if ( !$cbc ) {

## Encription failed, print message
        $self->message_failed_passwd();

## Return Error
        return 0;
    }

## Return CBC instance
    return $cbc;

} ## end sub open_keyfile

##############################################################################
#
# open_sitefile()
#
# Open Default Site's Identifier File
#
# Return Site's Identifier in Upper Case
# - 0         - if site's Identifier file not found or openning failed
#
# Arguments expected are:
# - self      - Object reference

sub open_sitefile {
    my ($self) = @_;

## Open file with Site identifier
    my $sitefile = $self->get_file('site');

## If sitefile not specifed, return Error
    return 0 if !$sitefile;

## Get ASF directory
    my $asfdir = $self->get_directory('site');

## Open sitefile
    if ( !open( SITEID, "< $asfdir/$sitefile" ) ) {

## If sitefile not found, return Error
        $self->message_failed_sitefile($sitefile);

## Return Error
        return 0;
    }

## Get Sitefile content
    my $site_id = join( '', <SITEID> );
    close(SITEID);
    chomp($site_id);

## Remove line feeds, new lines, spaces
    $site_id =~ s/[\n\r\s]+//g;

## If site_id is empty, return Error
    return 0 if !$site_id;

## Return Site's Identifier in Upper Case
    return uc($site_id);

} ## end sub open_sitefile

##############################################################################
#
# purge()
#
# Purge old logfiles and ARFs
#
# Arguments expected are:
# - self    - Object reference

sub purge {
    my ($self) = @_;

## Get log directory
    my $logdir = $self->get_directory('log');

## Define purge time
    my $oldtime = 86400 * 7;

## Open directory for removing old logs
    if ( opendir( DIR, $logdir ) ) {

## Find ATF
        my @logfiles = grep( /\.log$/, readdir(DIR) );

## Check all log files
        foreach my $logfile (@logfiles) {

## Check last modify time
            my $mtime = ( stat "$logdir/$logfile" )[9];

## Remove logfile if it older than a week
            unlink("$logdir/$logfile") if time - $oldtime > $mtime;

        }

## Close directory
        closedir(DIR);
    }

## Get ARF directory
    my $arfdir = $self->get_directory('current');

## Open directory for removing old ARF
    if ( opendir( DIR, $arfdir ) ) {

## Find ARF
        my @arfs = grep( /\.(xml|txt|html)$/, readdir(DIR) );

## Check all ARFs
        foreach my $arfile (@arfs) {

## Check last modify time
            my $mtime = ( stat "$arfdir/$arfile" )[9];

## Remove ARF if it older than a week
            unlink("$arfdir/$arfile") if time - $oldtime > $mtime;
        }

## Close directory
        closedir(DIR);
    }

## Return Ok
    return 1;

} ## end sub purge

##############################################################################
#
# set_sitefile()
#
# Set Default Site's Identifier File
#
# Return:
# - 1         - if site_id set as default
# - 0         - if site's Identifier file not specified or set failed
#
# Arguments expected are:
# - self      - Object reference
# - site_id   - Set default Site's Identifier

sub set_sitefile {
    my ( $self, $site_id ) = @_;

## Remove line feeds, new lines, spaces
    $site_id =~ s/[\n\r\s]+//g;

## Open file with Site identifier
    my $sitefile = $self->get_file('site');

## If sitefile not specifed, return Error
    return 0 if !$sitefile;

## Get ASF directory
    my $asfdir = $self->get_directory('site');

## Open sitefile
    if ( !open( SITEID, "> $asfdir/$sitefile" ) ) {

## If sitefile not found, return Error
        $self->message_failed_sitefile($sitefile);

## Return Error
        return 0;
    }

## Set Sitefile content
    print SITEID $site_id;
    close(SITEID);

## Return Site's Identifier in Upper Case
    return 1;

} ## end sub set_sitefile

##############################################################################
#
# update()
#
# Update ACF
#
# Return:
# - 1         - updated sucessfully
# - 0         - otherwise
#
# Arguments expected are:
# - self      - Object reference

sub update {
    my ($self) = @_;

## If ACF not Updated
    if ( !$self->{updated} ) {

## Print message: Up-to-date
        $self->message_uptodate();

## Return Error
        return 0;
    }

## Save ACF
    eval {
        $self->{xs}->XMLout(
            $self->{_xml},
            RootName   => 'config',
            OutputFile => $self->{path}
        );
    };

## Validate ACF
    my $error = $self->validate_file( $self->{path} );

## If validation failed
    if ($error) {

## Print Error message
        $self->message_failed_valid($error);

## Return Error
        return 0;
    }

## Empty update
    $self->{updated} = 0;

## Print ACF updated message
    $self->message_updated();

## Return Ok
    return 1;

} ## end sub update

##############################################################################
#
# validate_file()
#
# Validate File using XSD (XML Schema)
#
# Return:
# - error    - if exists
#
# Arguments expected are:
# - self     - Object reference
# - file     - File to validate

sub validate_file {
    my ( $self, $file ) = @_;

## Create a new validator object
    my $validator =
      XML::Validator::Schema->new( file => 'lib/AFX.xsd', cache => 1 );

## Create new parser object
    my $parser = XML::SAX::ParserFactory->parser( Handler => $validator );

## Validate file
    eval { $parser->parse_uri($file) };

## Return Error, if happend
    return "$@";

} ## end sub validate_file

##############################################################################
#
# validate_string()
#
# Validate String using XSD (XML Schema)
#
# Return:
# - error    - if exists
#
# Arguments expected are:
# - self     - Object reference
# - xml      - XML to validate

sub validate_string {
    my ( $self, $xml ) = @_;

## Create a new validator object
    my $validator =
      XML::Validator::Schema->new( file => 'lib/AFX.xsd', cache => 1 );

## Create new parser object
    my $parser = XML::SAX::ParserFactory->parser( Handler => $validator );

## Validate file
    eval { $parser->parse_string($xml) };

## Return Error, if happend
    return "$@";

} ## end sub validate_string

1;
